﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel;
using Substrate;

namespace MCValidator
{
    public class BackgroundValidator : BackgroundWorker
    {
        public IList<Error> ErrorList = new List<Error>();
        public object Item { get; private set; }

        public BackgroundValidator()
            : base()
        {
            this.DoWork += regionBGW_DoWork;
        }

        public void ReportError(Error e)
        {
            ErrorList.Add(e);
        }

        public new void RunWorkerAsync(object o)
        {
            Item = o;
            base.RunWorkerAsync();
        }

        /// <summary>
        /// This does the bulk of trhe checking
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void regionBGW_DoWork(object sender, DoWorkEventArgs evt)
        {
            BackgroundValidator worker = sender as BackgroundValidator;
            Substrate.Region region = worker.Item as Substrate.Region;

            // Contruct a list of tile entities to check the ids of - we need to avoid pistons
            List<int> entitiesToCheck = new List<int>();
            entitiesToCheck.AddRange(BlockType.TileEntities);
            // Pistons always seem to have mismatches with the block IDs
            entitiesToCheck.Remove(BlockType.PISTON);
            entitiesToCheck.Remove(BlockType.PISTON_HEAD);
            entitiesToCheck.Remove(BlockType.PISTON_MOVING);

            // Iterate everything to check it
            for (int regionX = 0; regionX < region.XDim; regionX++)
            {
                for (int regionZ = 0; regionZ < region.ZDim; regionZ++)
                {
                    ChunkRef chunk = region.GetChunkRef(regionX, regionZ);

                    if (worker.CancellationPending)
                    {
                        evt.Cancel = true;
                        return;
                    }

                    int currChunk = regionX * region.XDim + regionZ;
                    int numChunks = region.XDim * region.ZDim;
                    worker.ReportProgress((100 * currChunk) / numChunks);

                    if (chunk == null)
                    {
                        // Skip any empty chunks
                        continue;
                    }

                    // Check the chunk location matches the region's
                    {
//#error may need to get the chunk's private _cx, _cz rather than going through teh API - otherwise we get it from the containe. We're trying to check that the container and chunk match
                        if (chunk._cx != chunk.LocalX || chunk._cz != chunk.LocalZ ||
                            chunk._cx != regionX || chunk._cz != regionZ)
                        {
                            // convert coords into world coords
                            string msg = String.Format("Chunk at ({0},{1}), stored at: ({3}, (4))", chunk.X, chunk.Z, regionX, regionZ);
                            BackgroundValidator.Error e = new BackgroundValidator.Error(msg, region);
                            e.Actions.Add(new BackgroundValidator.DeleteChunkAction(region, chunk));
                            //e.Actions.Add(new BackgroundValidator.FixRegionOffsets(region, chunk));
                            worker.ReportError(e);
                        }
                    }

                    AlphaBlockCollection blocks = chunk.Blocks;
                    //return true;
                    // You could hardcode your dimensions, but maybe some day they
                    // won't always be 16.  Also the CLR is a bit stupid and has
                    // trouble optimizing repeated calls to Chunk.Blocks.xx, so we
                    // cache them in locals
                    int xdim = blocks.XDim;
                    int ydim = blocks.YDim;
                    int zdim = blocks.ZDim;


                    // x, z, y is the most efficient order to scan blocks (not that
                    // you should care about internal detail)
                    for (int x = 0; x < xdim; x++)
                    {
                        for (int z = 0; z < zdim; z++)
                        {
                            for (int y = 0; y < ydim; y++)
                            {
                                // Make sure that the block and chunk match up
                                int id = blocks.GetID(x, y, z);
                                TileEntity te = blocks.GetTileEntity(x, y, z);

                                // Check IDs are sensible
                                if (id > BlockType.MAX_BLOCK_IDS)
                                {
                                    // convert coords into world coords
                                    string msg = String.Format("Invalid Block ID ({0},{1},{2}), with ID: ({3})", chunk.X + x, y, chunk.Z + z, id);
                                    BackgroundValidator.Error e = new BackgroundValidator.Error(msg, region);
                                    e.Actions.Add(new BackgroundValidator.DeleteBlockAction(region, chunk, blocks.GetBlockRef(x, y, z)));
                                    e.Actions.Add(new BackgroundValidator.DeleteChunkAction(region, chunk));
                                    worker.ReportError(e);
                                }
                              
                                bool idShouldHaveTileEntity = entitiesToCheck.Contains(id);

                                // make sure that the block ID has an Tile entity if it should
                                if (te == null && idShouldHaveTileEntity)
                                {
                                    string msg = String.Format("Block ID indicates it shouldn't have a Tile Entity, but it does: ({0},{1},{2}), ID: ({3})", chunk.X + x, y, chunk.Z + z, id);
                                    BackgroundValidator.Error e = new BackgroundValidator.Error(msg, region);
                                    e.Actions.Add(new BackgroundValidator.DeleteBlockAction(region, chunk, blocks.GetBlockRef(x, y, z)));
                                    e.Actions.Add(new BackgroundValidator.DeleteChunkAction(region, chunk));
                                    worker.ReportError(e);
                                }

                                // make sure that the block ID doesn't have a Tile entity if it shouldn't
                                if (te != null && !idShouldHaveTileEntity)
                                {
                                    string msg = String.Format("Block ID indicates it should have a Tile Entity, but it doesn't: ({0},{1},{2}), ID: ({3})", chunk.X + x, y, chunk.Z + z, id);
                                    BackgroundValidator.Error e = new BackgroundValidator.Error(msg, region);
                                    e.Actions.Add(new BackgroundValidator.DeleteBlockAction(region, chunk, blocks.GetBlockRef(x, y, z)));
                                    e.Actions.Add(new BackgroundValidator.DeleteChunkAction(region, chunk));
                                    worker.ReportError(e);
                                }


                                // Check that the type id matches the Tile Entity id (if there is one)
                                if (te != null)
                                {
                                    if (!te.isValidBlockID(id))
                                    {
                                        string msg = String.Format("Corrupt Tile Entity ({0},{1},{2}), {3} with ID: ({4})", chunk.X + x, y, chunk.Z + z, te.ID, id);
                                        BackgroundValidator.Error e = new BackgroundValidator.Error(msg, region);
                                        e.Actions.Add(new BackgroundValidator.DeleteBlockAction(region, chunk, blocks.GetBlockRef(x, y, z)));
                                        e.Actions.Add(new BackgroundValidator.DeleteChunkAction(region, chunk));
                                        worker.ReportError(e);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }



        /// <summary>
        /// A validator error
        /// </summary>
        public class Error
        {
            public Error(string desc, Region r)
            {
                Description = desc;
                Region = r;
                Actions = new List<Action>();
            }
            public string Description { get; protected set; }
            public Region Region { get; protected set; }
            public IList<Action> Actions { get; protected set; }

            public Action GetAction(string name)
            {
                foreach (Action a in Actions)
                {
                    if (a.Description == name)
                        return a;
                }
                return null;
            }
        }

        /// <summary>
        /// Base class for actions
        /// </summary>
        public abstract class Action
        {
            public Action(string desc)
            {
                Description = desc;
            }

            public string Description {get; protected set;}
            public delegate bool PerformActionHandler();
            public PerformActionHandler Perform;
        }

        /// <summary>
        /// An action that pops up a message box
        /// </summary>
        public class MsgBoxAction : Action
        {
            public MsgBoxAction(string msg)
                : base("Show Message")
            {
                _msg = msg;
                Perform = this.popUp;
            }

            string _msg;

            private bool popUp()
            {
                System.Windows.Forms.MessageBox.Show(_msg);
                return true;
            }
        }

        public class DeleteChunkAction : Action
        {
          public DeleteChunkAction(Region r, ChunkRef c)
            : base("Delete Chunk")
          {
            _r = r;
            _c = c;
            Perform = this.deleteChunk;
          }

          Region _r;
          ChunkRef _c;

          private bool deleteChunk()
          {
            // TODO: What if a solution for a different error resolved this? Rescan after each fix? Flag crtain solutions as needing a rescan or chunk, region or world?
            _r.DeleteChunk(_c.LocalX, _c.LocalZ);
            return true;
          }
        }

        public class DeleteBlockAction : Action
        {
            public DeleteBlockAction(Region r, ChunkRef c, AlphaBlockRef b)
                : base("Delete Block")
            {
                _r = r;
                _c = c;
                _b = b;
                Perform = this.deleteBlock;
            }

            Region _r;
            ChunkRef _c;
            AlphaBlockRef _b;

            private bool deleteBlock()
            {
                // TODO: What if a solution for a different error resolved this? Rescan after each fix? Flag crtain solutions as needing a rescan or chunk, region or world?
                // HACK: Grab the chunk again to make sure it's in the cache - otherwise it won't be saved
                ChunkRef c = _r.GetChunkRef(_c.LocalX, _c.LocalZ);
                AlphaBlockCollection b = c.Blocks;
                //b.SetID(_b._index, 0);
                return true;
            }
        }
        /*
         * Move to Location 'solution' could be implemented like:
    PlayerManager pm = world.GetPlayerManager();

    Player p = pm.GetPlayer("StevilKnevil");
    p.Position.X = 8;
    p.Position.Y = 65;
    p.Position.Z = -3;
    pm.SetPlayer("StevilKnevil",p);
         */



    }
}
